//-------------------------------------------------------------------------------------------------
// Copyright (c) Bradford W. Mott and Flare Contributors
// North Carolina State University, Department of Computer Science
// The IntelliMedia Group
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//-------------------------------------------------------------------------------------------------

using ICloneable = System.ICloneable;
using System.Collections.Generic;
using Flare.Display;

namespace Flare.Events
{
    public delegate void EventListener(Event evt);

    /// <summary>
    /// The EventDispatcher class provides a base class for all classes that dispatch events,
    /// such as the DisplayObject class.
    /// </summary>
    public class EventDispatcher : ICloneable
    {
        private static int dispatchRecursionDepth = 0;

        private Dictionary<string, List<KeyValuePair<int, EventListener>>> m_capturedEventMap;
        private Dictionary<string, List<KeyValuePair<int, EventListener>>> m_notCapturedEventMap;

        /// <summary>
        /// Return a clone of the EventDispatcher with any event listeners copied such that
        /// they taget the clone.
        /// </summary>
        public virtual object Clone()
        {
            EventDispatcher clone = (EventDispatcher)this.MemberwiseClone();

            if (this.m_capturedEventMap != null)
            {
                clone.m_capturedEventMap = new Dictionary<string, List<KeyValuePair<int, EventListener>>>();
                foreach (var e1 in this.m_capturedEventMap)
                {
                    List<KeyValuePair<int, EventListener>> list = new List<KeyValuePair<int, EventListener>>();
                    foreach (var e2 in e1.Value)
                    {
                        list.Add(new KeyValuePair<int, EventListener>(e2.Key, 
                            (EventListener)System.Delegate.CreateDelegate(typeof(EventListener),
                                clone, e2.Value.Method)));
                    }
                    clone.m_capturedEventMap.Add(e1.Key, list);
                }
            }

            if (this.m_notCapturedEventMap != null)
            {
                clone.m_notCapturedEventMap = new Dictionary<string, List<KeyValuePair<int, EventListener>>>();
                foreach (var e1 in this.m_notCapturedEventMap)
                {
                    List<KeyValuePair<int, EventListener>> list = new List<KeyValuePair<int, EventListener>>();
                    foreach (var e2 in e1.Value)
                    {
                        list.Add(new KeyValuePair<int, EventListener>(e2.Key, 
                            (EventListener)System.Delegate.CreateDelegate(typeof(EventListener),
                                clone, e2.Value.Method)));
                    }
                    clone.m_notCapturedEventMap.Add(e1.Key, list);
                }
            }

            return clone;
        }

        /// <summary>
        /// Add an event listener to the dispatcher so that the listener will receive
        /// notifications of the event.
        /// </summary>
        public void AddEventListener(string type, EventListener listener,
            bool useCapture = false, int priority = 0)
        {
            // Ensure this EventDispatcher is listed in the dispatcher queue
            if (NativeWindow.windowActiveForScripts != null)
            {
                NativeWindow.windowActiveForScripts.dispatcherQueue.Add(type, this);
            }
            else
            {
                // NativeWindow.windowActiveForScripts should never be null
                throw new global::System.InvalidOperationException();
            }

            // Create dictionary if it doesn't exist
            if (useCapture)
            {
                if (m_capturedEventMap == null)
                {
                    m_capturedEventMap =
                        new Dictionary<string, List<KeyValuePair<int, EventListener>>>();
                }
            }
            else
            {
                if (m_notCapturedEventMap == null)
                {
                    m_notCapturedEventMap =
                        new Dictionary<string, List<KeyValuePair<int, EventListener>>>();
                }
            }

            var map = useCapture ? m_capturedEventMap : m_notCapturedEventMap;

            if (!map.ContainsKey(type))
            {
                // No listerners for this type of event so create a new list and add
                // this listener
                List<KeyValuePair<int, EventListener>> list =
                    new List<KeyValuePair<int, EventListener>>();
                list.Add(new KeyValuePair<int, EventListener>(priority, listener));
                map[type] = list;
            }
            else
            {
                // A list of listeners already exists so find the correct place to insert
                // this listener based on its priority
                List<KeyValuePair<int, EventListener>> list = map[type];
                int insertIndex = 0;
                for (int i = 0 ; i < list.Count; ++i)
                {
                    if (list[i].Value == listener)
                    {
                        // listener is already added so we should exit and not add again
                        return;
                    }
                    if (list[i].Key >= priority)
                    {
                        insertIndex = i;
                    }
                }
                list.Insert(insertIndex, new KeyValuePair<int, EventListener>(priority, listener));
            }
        }

        /// <summary>
        /// Dispatches the event into the event flow.
        /// </summary>
        public bool DispatchEvent(Event evt)
        {
            if (dispatchRecursionDepth > 2048)
            {
                throw new global::System.StackOverflowException(
                    "Event dispatch recursion overflow.");
            }

            ++dispatchRecursionDepth;

            evt = evt.Clone();
            evt.target = this;

            evt.eventPhase = EventPhase.CAPTURING_PHASE;
            DispatchEventCapturingPhase(evt);

            evt.eventPhase = EventPhase.AT_TARGET;
            DispatchEventAtTargetPhase(evt);

            if (evt.bubbles)
            {
                evt.eventPhase = EventPhase.BUBBLING_PHASE;
                DispatchEventBubblingPhase(evt);
            }

            --dispatchRecursionDepth;

            return !(evt.defaultPrevented);
        }

        /// <summary>
        /// Returns true if the dispatcher has any listeners for the specific event type.
        /// </summary>
        public bool HasEventListener(string type)
        {
            if ((m_notCapturedEventMap != null) && m_notCapturedEventMap.ContainsKey(type))
            {
                if (m_notCapturedEventMap[type].Count > 0)
                {
                    return true;
                }
            }

            if ((m_capturedEventMap != null) && m_capturedEventMap.ContainsKey(type))
            {
                if (m_capturedEventMap[type].Count > 0)
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Removes the listener from the dispatcher.
        /// </summary>
        public void RemoveEventListener(string type, EventListener listener,
            bool useCapture = false)
        {
            var map = useCapture ? m_capturedEventMap : m_notCapturedEventMap;

            if ((map != null) && map.ContainsKey(type))
            {
                List<KeyValuePair<int, EventListener>> list = map[type];
                if (listener == null)
                {
                    list.Clear();
                }
                else
                {
                    for (int i = 0 ; i < list.Count; ++i)
                    {
                        if (list[i].Value == listener)
                        {
                            list.RemoveAt(i);
                            break;
                        }
                    }
                }
            }

            // If this EventDispatcher is nolonger listening for this type of event then
            // remove it from the dispatcher queue
            if (((m_capturedEventMap == null) || !m_capturedEventMap.ContainsKey(type)) &&
                ((m_notCapturedEventMap == null) || !m_notCapturedEventMap.ContainsKey(type)))
            {
                if (NativeWindow.windowActiveForScripts != null)
                {
                    NativeWindow.windowActiveForScripts.dispatcherQueue.Remove(type, this);
                }
                else
                {
                    // NativeWindow.windowActiveForScripts should never be null
                    throw new global::System.InvalidOperationException();
                }
            }
        }

        /// <summary>
        /// Returns true if the dispatcher (or if any of its children) has a listener
        /// for the specified event type.
        /// </summary>
        public bool WillTrigger(string type)
        {
            if (HasEventListener(type))
            {
                return true;
            }
            else
            {
                if (this is DisplayObject)
                {
                    var obj = this as DisplayObject;
                    if (obj.parent != null)
                    {
                        return obj.parent.WillTrigger(type);
                    }
                }

                return false;
            }
        }

        internal void DispatchEventCapturingPhase(Event evt)
        {
            if (this is DisplayObject)
            {
                if (((DisplayObject)this).parent != null)
                {
                    ((DisplayObject)this).parent.DispatchEventCapturingPhase(evt);
                }

                evt.currentTarget = this;

                if (!evt.propagationStopped && (evt.target != evt.currentTarget))
                {
                    List<EventListener> list = Listeners(evt.type, true);
                    foreach (var listener in list)
                    {
                        listener(evt);

                        if (evt.immediatePropagationStopped)
                        {
                            break;
                        }
                    }
                }
            }
        }

        internal void DispatchEventAtTargetPhase(Event evt)
        {
            evt.currentTarget = this;
            evt.target = this;

            if (!evt.propagationStopped)
            {
                List<EventListener> list = Listeners(evt.type, false);
                foreach (var listener in list)
                {
                    listener(evt);

                    if (evt.immediatePropagationStopped)
                    {
                        break;
                    }
                }
            }
        }

        internal void DispatchEventBubblingPhase(Event evt)
        {
            if (this is DisplayObject)
            {
                evt.currentTarget = this;

                if (!evt.propagationStopped && (evt.target != evt.currentTarget))
                {
                    List<EventListener> list = Listeners(evt.type, false);
                    foreach (var listener in list)
                    {
                        listener(evt);

                        if (evt.immediatePropagationStopped)
                        {
                            break;
                        }
                    }
                }

                if (!evt.propagationStopped && (((DisplayObject)this).parent != null))
                {
                    ((DisplayObject)this).parent.DispatchEventBubblingPhase(evt);
                }
            }
        }

        protected List<EventListener> Listeners(string type, bool capturePhase)
        {
            List<EventListener> result = new List<EventListener>();

            var map = capturePhase ? m_capturedEventMap : m_notCapturedEventMap;
            if ((map != null) && map.ContainsKey(type))
            {
                List<KeyValuePair<int, EventListener>> list = map[type];
                foreach (var entry in list)
                {
                    result.Add(entry.Value);
                }
            }

            return result;
        }
    }
}
